$url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 3,
CURLOPT_CONNECTTIMEOUT => 2
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return ($http_code === 200 && $response) ? json_decode($response, true) : null;
}
function calculateBackgroundColor() {
$lat = 50.8009; $lon = 11.5875;
$dayColor = [224, 224, 224];
$nightColor = [96, 96, 96];
$twilightDuration = 2;
$now = time();
$sunrise = date_sunrise($now, SUNFUNCS_RET_TIMESTAMP, $lat, $lon);
$sunset = date_sunset($now, SUNFUNCS_RET_TIMESTAMP, $lat, $lon);
$current_hour = (float)date('G') + ((float)date('i') / 60);
$sunrise_hour = (float)date('G', $sunrise) + ((float)date('i', $sunrise) / 60);
$sunset_hour = (float)date('G', $sunset) + ((float)date('i', $sunset) / 60);
$t = 0;
if ($current_hour >= $sunset_hour && $current_hour <= $sunset_hour + $twilightDuration) {
$t = ($current_hour - $sunset_hour) / $twilightDuration;
} elseif ($current_hour >= $sunrise_hour - $twilightDuration && $current_hour < $sunrise_hour) {
$t = 1 - (($current_hour - ($sunrise_hour - $twilightDuration)) / $twilightDuration);
} elseif ($current_hour < $sunrise_hour - $twilightDuration || $current_hour > $sunset_hour + $twilightDuration) {
$t = 1;
}
$r = round($dayColor[0] + ($nightColor[0] - $dayColor[0]) * $t);
$g = round($dayColor[1] + ($nightColor[1] - $dayColor[1]) * $t);
$b = round($dayColor[2] + ($nightColor[2] - $dayColor[2]) * $t);
return "rgb($r,$g,$b)";
}
function calculateBatteryRuntime($usable_energy, $power_display, $battery_reserve_percent) {
if ($power_display >= 0 || $usable_energy <= 0) return '-';
$power_kw = abs($power_display) / 1000;
if ($power_kw <= 0) return '-';
$runtime_hours = $usable_energy / $power_kw;
$end_time = time() + ($runtime_hours * 3600);
$german_weekdays = ['Sun' => 'So', 'Mon' => 'Mo', 'Tue' => 'Di', 'Wed' => 'Mi', 'Thu' => 'Do', 'Fri' => 'Fr', 'Sat' => 'Sa'];
$weekday_german = $german_weekdays[date('D', $end_time)] ?? date('D', $end_time);
return date('H:i', $end_time) . " Uhr
" . $weekday_german . ". " . date('d.m.y', $end_time);
}
function getBatteryStatusText($power_display, $usable_soc) {
if ($power_display > 0) return ['label' => 'Akku:', 'value' => 'wird geladen'];
if ($power_display == 0 && $usable_soc > 0 && $usable_soc <= 2) return ['label' => 'Akku:', 'value' => 'warten auf Ladung'];
if ($usable_soc <= 0) return ['label' => 'Akku ist leer:', 'value' => 'Netzbezug'];
return ['label' => 'Akku reicht bei aktuellem
Verbrauch bis circa:', 'value' => ''];
}
function getBatteryIcon($soc) {
if ($soc <= 15) return '';
if ($soc <= 25) return '';
if ($soc <= 50) return '';
if ($soc <= 75) return '';
return '';
}
function format_duration($seconds) { return (!$seconds || $seconds <= 0) ? "–" : sprintf("%02d:%02d:%02d", floor($seconds / 3600), floor(($seconds % 3600) / 60), $seconds % 60); }
function translate_mode($mode) { return ['off'=>'Aus','pv'=>'PV','minpv'=>'Min+PV','now'=>'Schnell'][strtolower($mode)] ?? ucfirst($mode); }
// Funktion: Berechnung der Ladezeit bis 100 % unter Berücksichtigung der Sonnenzeit
function calculateBatteryChargeTime($current_soc, $capacity_kwh, $charge_power_w, $battery_reserve_percent = 10) {
// Wenn nicht geladen wird oder Ladeleistung <= 0
if ($charge_power_w <= 0) return null;
// Wenn bereits voll (über Reserve hinaus)
$usable_soc = $current_soc - $battery_reserve_percent;
if ($usable_soc >= 100) return 'Bereits voll';
// Benötigte Energie bis 100 % (Gesamtkapazität, nicht nur nutzbar)
$remaining_kwh = $capacity_kwh * (100 - $current_soc) / 100;
$charge_power_kw = $charge_power_w / 1000;
if ($remaining_kwh <= 0) return null;
// Benötigte Stunden bis 100 %
$hours_needed = $remaining_kwh / $charge_power_kw;
// Sonnenuntergang und maximale Ladezeit heute berechnen
$lat = 50.8009; $lon = 11.5875; // Koordinaten für Kahla, Thüringen
$now = time();
$sunset = date_sunset($now, SUNFUNCS_RET_TIMESTAMP, $lat, $lon);
$sunrise = date_sunrise($now, SUNFUNCS_RET_TIMESTAMP, $lat, $lon);
// Wenn aktuell nach Sonnenuntergang oder vor Sonnenaufgang -> keine PV-Ladung mehr möglich
$is_daytime = ($now >= $sunrise && $now <= $sunset);
if (!$is_daytime) {
// Nachts: Nur mit Netzladung möglich (falls aktiv)
// Da wir keine Netzladung-Info haben, zeigen wir nur die reine Zeit an
$end_time = $now + ($hours_needed * 3600);
return '100 % ca. ' . date('H:i', $end_time) . ' Uhr';
}
// Verbleibende Sonnenstunden heute
$remaining_sun_hours = max(0, ($sunset - $now) / 3600);
// Maximale Energie, die heute noch von der Sonne geladen werden kann
$max_solar_energy_today = $charge_power_kw * $remaining_sun_hours;
if ($remaining_kwh <= $max_solar_energy_today) {
// Heute schaffen wir 100 %
$end_time = $now + ($hours_needed * 3600);
return '100 % ca. ' . date('H:i', $end_time) . ' Uhr';
} else {
// Heute schaffen wir nicht 100 % - berechne maximalen SoC heute
$max_soc_today = $current_soc + (($max_solar_energy_today / $capacity_kwh) * 100);
$max_soc_today = min(100, round($max_soc_today));
// Nutzbaren SoC (abzgl. Reserve) anzeigen
$usable_max_soc = max(0, $max_soc_today - $battery_reserve_percent);
if ($max_soc_today >= 100) {
// Sonderfall: eigentlich reicht es doch, aber Rundungsfehler
$end_time = $now + ($hours_needed * 3600);
return '100 % ca. ' . date('H:i', $end_time) . ' Uhr';
} elseif ($usable_max_soc <= 0) {
return 'Heute keine nennenswerte Ladung mehr möglich';
} else {
return 'max. ' . $max_soc_today . ' % (' . $usable_max_soc . '% nutzbar) ca. ' . date('H:i', $sunset) . ' Uhr';
}
}
}
// Funktion zur Berechnung der Sonnenscheindauer
function getSunInfo() {
$lat = 50.8009; $lon = 11.5875;
$now = time();
$sunrise = date_sunrise($now, SUNFUNCS_RET_TIMESTAMP, $lat, $lon);
$sunset = date_sunset($now, SUNFUNCS_RET_TIMESTAMP, $lat, $lon);
$sunrise_time = date('H:i', $sunrise);
$sunset_time = date('H:i', $sunset);
// Gesamte Sonnenscheindauer heute
$sun_duration_seconds = $sunset - $sunrise;
$sun_duration_hours = floor($sun_duration_seconds / 3600);
$sun_duration_minutes = floor(($sun_duration_seconds % 3600) / 60);
$sun_duration = sprintf("%02d:%02d", $sun_duration_hours, $sun_duration_minutes);
// Verbleibende Sonnenscheindauer heute
$remaining_seconds = max(0, $sunset - $now);
$remaining_hours = floor($remaining_seconds / 3600);
$remaining_minutes = floor(($remaining_seconds % 3600) / 60);
$remaining_duration = sprintf("%02d:%02d", $remaining_hours, $remaining_minutes);
return [
'sunrise' => $sunrise_time,
'sunset' => $sunset_time,
'duration' => $sun_duration,
'remaining' => $remaining_duration
];
}
// Datenabruf
$data_3em = fetchShellyData($endpoint_3em);
$data_pv = fetchShellyData($endpoint_pv);
$data_3em_63t = fetchShellyData($endpoint_3em_63t);
$data_3em_63w = fetchShellyData($endpoint_3em_63w);
$evcc_data = @file_get_contents($evcc_url);
$data_evcc = $evcc_data ? json_decode($evcc_data, true) : null;
$loadpoint = $data_evcc['loadpoints'][0] ?? [];
// Batterie-Daten aus dem neuen EVCC-Format auslesen
// Das neue Format hat battery direkt auf oberster Ebene
$battery = null;
if ($data_evcc && isset($data_evcc['battery']) && is_array($data_evcc['battery'])) {
// Neues Format: battery ist direkt ein Array mit power, capacity, soc
$battery = $data_evcc['battery'];
} elseif ($data_evcc && isset($data_evcc['battery'][0]) && is_array($data_evcc['battery'][0])) {
// Fallback: falls battery ein Array von Geräten ist, nimm das erste
$battery = $data_evcc['battery'][0];
}
// Strom + Phasen aus EVCC
$avg_current = null;
$phase_text = '';
if ($data_evcc && !empty($loadpoint)) {
$currents = $loadpoint['chargeCurrents'] ?? null;
if (is_array($currents) && count($currents) == 3) {
$valid_currents = array_filter($currents, fn($c) => $c > 0.1);
$avg_current = !empty($valid_currents) ? array_sum($valid_currents) / count($valid_currents) : 0;
}
$phases_active = $loadpoint['phasesActive'] ?? 0;
if ($phases_active == 1) $phase_text = "1‑phasig";
elseif ($phases_active == 3) $phase_text = "3‑phasig";
elseif ($phases_active > 0) $phase_text = $phases_active . "‑phasig";
}
// Netzanschluss-Daten
if ($data_3em && isset($data_3em["em:0"])) {
$em1 = $data_3em["em:0"];
$act_power_3em = (float)$em1["total_act_power"];
$voltage_avg_3em = ($em1["a_voltage"] + $em1["b_voltage"] + $em1["c_voltage"]) / 3;
$freq_3em = (float)$em1["a_freq"];
$pf_avg_3em = ($em1["a_pf"] + $em1["b_pf"] + $em1["c_pf"]) / 3;
$eff_current_3em = $voltage_avg_3em > 0 ? abs($act_power_3em / $voltage_avg_3em) : 0;
$act_power_f_3em = number_format($act_power_3em, 1, ',', '.');
$voltage_f_3em = number_format($voltage_avg_3em, 1, ',', '');
$freq_f_3em = number_format($freq_3em, 2, ',', '');
$pf_f_3em = number_format($pf_avg_3em, 2, ',', '');
$eff_current_f_3em = number_format($eff_current_3em, 2, ',', '');
} else {
$act_power_3em = 0;
$act_power_f_3em = $voltage_f_3em = $freq_f_3em = $pf_f_3em = $eff_current_f_3em = "Fehler";
}
// PV-Daten
if ($data_pv) {
$act_power_pv = abs($data_pv['em1:0']['act_power'] ?? 0);
$current_pv = number_format(($data_pv['em1:0']['current'] ?? 0), 2, ',', '');
$voltage_pv = number_format(($data_pv['em1:0']['voltage'] ?? 0), 1, ',', '');
$freq_pv = number_format(($data_pv['em1:0']['freq'] ?? 0), 2, ',', '');
$pf_pv = number_format(($data_pv['em1:0']['pf'] ?? 0), 2, ',', '');
$power_per_kWp = $pv_kWp > 0 ? $act_power_pv / $pv_kWp : 0;
$power_per_kWp_f = number_format($power_per_kWp, 1, ',', '.');
$act_power_formatted_pv = number_format($act_power_pv, 1, ',', '.');
} else {
$act_power_pv = 0;
$act_power_formatted_pv = $current_pv = $voltage_pv = $freq_pv = $pf_pv = $power_per_kWp_f = "Fehler";
}
// 3EM-63T Daten
if ($data_3em_63t) {
if (isset($data_3em_63t["em1:0"])) {
$em1_63t = $data_3em_63t["em1:0"];
$act_power_3em_63t = abs($em1_63t["act_power"] ?? 0);
$current_avg_3em_63t = (($em1_63t["a_current"] ?? 0) + ($em1_63t["b_current"] ?? 0) + ($em1_63t["c_current"] ?? 0)) / 3;
$voltage_avg_3em_63t = (($em1_63t["a_voltage"] ?? 0) + ($em1_63t["b_voltage"] ?? 0) + ($em1_63t["c_voltage"] ?? 0)) / 3;
$freq_3em_63t = $em1_63t["a_freq"] ?? $em1_63t["freq"] ?? 0;
$pf_avg_3em_63t = (($em1_63t["a_pf"] ?? 0) + ($em1_63t["b_pf"] ?? 0) + ($em1_63t["c_pf"] ?? 0)) / 3;
} elseif (isset($data_3em_63t["em:0"])) {
$em1_63t = $data_3em_63t["em:0"];
$act_power_3em_63t = abs($em1_63t["total_act_power"] ?? 0);
$current_avg_3em_63t = ($em1_63t["a_current"] + $em1_63t["b_current"] + $em1_63t["c_current"]) / 3;
$voltage_avg_3em_63t = ($em1_63t["a_voltage"] + $em1_63t["b_voltage"] + $em1_63t["c_voltage"]) / 3;
$freq_3em_63t = $em1_63t["a_freq"] ?? 0;
$pf_avg_3em_63t = ($em1_63t["a_pf"] + $em1_63t["b_pf"] + $em1_63t["c_pf"]) / 3;
} else {
foreach ($data_3em_63t as $key => $value) {
if (strpos($key, 'em') === 0 && is_array($value)) {
$em1_63t = $value;
$act_power_3em_63t = abs($em1_63t["act_power"] ?? $em1_63t["total_act_power"] ?? 0);
$current_avg_3em_63t = $em1_63t["a_current"] ?? $em1_63t["current"] ?? 0;
$voltage_avg_3em_63t = $em1_63t["a_voltage"] ?? $em1_63t["voltage"] ?? 0;
$freq_3em_63t = $em1_63t["a_freq"] ?? $em1_63t["freq"] ?? 0;
$pf_avg_3em_63t = $em1_63t["a_pf"] ?? $em1_63t["pf"] ?? 0;
break;
}
}
}
$act_power_f_3em_63t = number_format($act_power_3em_63t, 1, ',', '.');
$current_f_3em_63t = number_format($current_avg_3em_63t, 2, ',', '');
$voltage_f_3em_63t = number_format($voltage_avg_3em_63t, 1, ',', '');
$freq_f_3em_63t = number_format($freq_3em_63t, 2, ',', '');
$pf_f_3em_63t = number_format($pf_avg_3em_63t, 2, ',', '');
$power_per_kWp_63t = $pv_kWp_63t > 0 ? $act_power_3em_63t / $pv_kWp_63t : 0;
$power_per_kWp_f_63t = number_format($power_per_kWp_63t, 1, ',', '.');
} else {
$act_power_3em_63t = 0;
$act_power_f_3em_63t = $current_f_3em_63t = $voltage_f_3em_63t = $freq_f_3em_63t = $pf_f_3em_63t = $power_per_kWp_f_63t = "Fehler";
}
// 3EM-63W Daten
if ($data_3em_63w) {
if (isset($data_3em_63w["em1:0"])) {
$em1_63w = $data_3em_63w["em1:0"];
$act_power_3em_63w = abs($em1_63w["act_power"] ?? 0);
$current_avg_3em_63w = (($em1_63w["a_current"] ?? 0) + ($em1_63w["b_current"] ?? 0) + ($em1_63w["c_current"] ?? 0)) / 3;
$voltage_avg_3em_63w = (($em1_63w["a_voltage"] ?? 0) + ($em1_63w["b_voltage"] ?? 0) + ($em1_63w["c_voltage"] ?? 0)) / 3;
$freq_3em_63w = $em1_63w["a_freq"] ?? $em1_63w["freq"] ?? 0;
$pf_avg_3em_63w = (($em1_63w["a_pf"] ?? 0) + ($em1_63w["b_pf"] ?? 0) + ($em1_63w["c_pf"] ?? 0)) / 3;
} elseif (isset($data_3em_63w["em:0"])) {
$em1_63w = $data_3em_63w["em:0"];
$act_power_3em_63w = abs($em1_63w["total_act_power"] ?? 0);
$current_avg_3em_63w = ($em1_63w["a_current"] + $em1_63w["b_current"] + $em1_63w["c_current"]) / 3;
$voltage_avg_3em_63w = ($em1_63w["a_voltage"] + $em1_63w["b_voltage"] + $em1_63w["c_voltage"]) / 3;
$freq_3em_63w = $em1_63w["a_freq"] ?? 0;
$pf_avg_3em_63w = ($em1_63w["a_pf"] + $em1_63w["b_pf"] + $em1_63w["c_pf"]) / 3;
} else {
foreach ($data_3em_63w as $key => $value) {
if (strpos($key, 'em') === 0 && is_array($value)) {
$em1_63w = $value;
$act_power_3em_63w = abs($em1_63w["act_power"] ?? $em1_63w["total_act_power"] ?? 0);
$current_avg_3em_63w = $em1_63w["a_current"] ?? $em1_63w["current"] ?? 0;
$voltage_avg_3em_63w = $em1_63w["a_voltage"] ?? $em1_63w["voltage"] ?? 0;
$freq_3em_63w = $em1_63w["a_freq"] ?? $em1_63w["freq"] ?? 0;
$pf_avg_3em_63w = $em1_63w["a_pf"] ?? $em1_63w["pf"] ?? 0;
break;
}
}
}
$act_power_f_3em_63w = number_format($act_power_3em_63w, 1, ',', '.');
$current_f_3em_63w = number_format($current_avg_3em_63w, 2, ',', '');
$voltage_f_3em_63w = number_format($voltage_avg_3em_63w, 1, ',', '');
$freq_f_3em_63w = number_format($freq_3em_63w, 2, ',', '');
$pf_f_3em_63w = number_format($pf_avg_3em_63w, 2, ',', '');
$power_per_kWp_63w = $pv_kWp_63w > 0 ? $act_power_3em_63w / $pv_kWp_63w : 0;
$power_per_kWp_f_63w = number_format($power_per_kWp_63w, 1, ',', '.');
} else {
$act_power_3em_63w = 0;
$act_power_f_3em_63w = $current_f_3em_63w = $voltage_f_3em_63w = $freq_f_3em_63w = $pf_f_3em_63w = $power_per_kWp_f_63w = "Fehler";
}
// EVCC-Daten verarbeiten
if ($data_evcc) {
$tarif_price = $data_evcc['tariffGrid'] ?? null;
// Anzeige: Preis kaufmännisch gerundet auf 2 Stellen (0,2219 -> 0,22)
$tarif_display = ($tarif_price !== null) ? "Ladekosten-Summe (gerundet): Preis aktuell: " . number_format(round($tarif_price, 2), 2, ',', '.') . " €/kWh." : "Tarif nicht verfügbar";
// Batterie-Daten aus dem neuen Format verwenden
if ($battery) {
$soc = $battery['soc'] ?? 0;
$battery_fill = max(0, min(100, $soc));
$capacity = $battery['capacity'] ?? 0;
$power_raw = $battery['power'] ?? 0;
$power_display = -1 * $power_raw;
} else {
$soc = 0;
$battery_fill = 0;
$capacity = 0;
$power_raw = 0;
$power_display = 0;
}
$connected = !empty($loadpoint['connected']);
$charging = !empty($loadpoint['charging']);
$power_evcc = $loadpoint['chargePower'] ?? 0;
$chargedEnergy = $loadpoint['chargedEnergy'] ?? 0;
$modeRaw = htmlspecialchars($loadpoint['mode'] ?? 'Unbekannt');
$mode = translate_mode($modeRaw);
$vehicle = htmlspecialchars($loadpoint['vehicleTitle'] ?? ($loadpoint['vehicleName'] ?? ''));
$duration = format_duration($loadpoint['chargeDuration'] ?? 0);
$charged_kwh = $chargedEnergy / 1000.0;
// Berechnung: exakt mit dem tatsächlichen Preis (0,2219), nicht mit dem gerundeten
$preis_anzeige = round($charged_kwh * $tarif_price, 2);
if ($charging) {
$statusText = "⚡ lädt";
$statusColor = "orange";
} elseif ($connected) {
$statusText = " verbunden";
$statusColor = "#222";
} else {
$statusText = "✅ frei";
$statusColor = "green";
}
$factor = $capacity / 100;
$energy_total = $soc * $factor;
$usable_soc = max(0, $soc - $battery_reserve_percent);
$usable_energy = $usable_soc * $factor;
$power_sign = $power_display >= 0 ? "+" : "-";
$soc_display = (round($soc, 1) == round($soc)) ? number_format($soc, 0, ',', '.') : number_format($soc, 1, ',', '.');
$usable_soc_display = (round($usable_soc, 1) == round($usable_soc)) ? number_format($usable_soc, 0, ',', '.') : number_format($usable_soc, 1, ',', '.');
$battery_runtime = calculateBatteryRuntime($usable_energy, $power_display, $battery_reserve_percent);
$battery_status = getBatteryStatusText($power_display, $usable_soc);
$battery_icon = getBatteryIcon($soc);
// Ladezeit-Berechnung für den Stromspeicher (nur wenn geladen wird)
$charge_time_info = '';
if ($battery && $power_display > 0) {
$charge_time_info = calculateBatteryChargeTime($soc, $capacity, $power_display, $battery_reserve_percent);
}
} else {
$statusText = "⚠️ EVCC nicht erreichbar";
$statusColor = "red";
$power_evcc = $soc = $battery_fill = 0;
$battery_runtime = '-';
$battery_status = ['label' => 'Akku:', 'value' => 'Daten nicht verfügbar'];
$battery_icon = '';
$mode = 'Unbekannt';
$duration = '–';
$tarif_display = "Tarif nicht verfügbar";
$preis_anzeige = 0;
$vehicle = '';
$connected = false;
$charging = false;
$charge_time_info = '';
}
$backgroundColor = calculateBackgroundColor();
$timestamp = date("d.m.Y H:i:s");
$title_power_3em = ($act_power_3em >= 0 ? '+' : '') . $act_power_f_3em;
$title_power_pv = ($act_power_pv > 0 ? '+' : '') . $act_power_formatted_pv;
$total_pv_power = $act_power_pv + $act_power_3em_63t + $act_power_3em_63w;
// Sonneninformationen abrufen
$sunInfo = getSunInfo();
// AJAX-Ausgabe
if ($isAjax) {
ob_clean();
?>